/*
 * Copyright (C) 2018 The Asiainfo-Ability Authors
 *
 *      Licensed under the Apache License, Version 2.0 (the "License");
 *      you may not use this file except in compliance with the License.
 *      You may obtain a copy of the License at
 *
 *          http://www.apache.org/licenses/LICENSE-2.0
 *
 *      Unless required by applicable law or agreed to in writing, software
 *      distributed under the License is distributed on an "AS IS" BASIS,
 *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *      See the License for the specific language governing permissions and
 *      limitations under the License.
 */

package org.asiainfo.ability.provider.configuration;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.netflix.discovery.converters.Auto;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.asiainfo.ability.base.service.TraceService;
import org.asiainfo.ability.base.utils.Response;
import org.asiainfo.ability.base.vo.CalleeTrace;
import org.asiainfo.ability.provider.annotation.AbilityFace;
import org.asiainfo.ability.provider.annotation.ApiProvider;
import org.asiainfo.ability.provider.code.ServiceErrorCode;
import org.asiainfo.ability.provider.custom.RequestContext;
import org.asiainfo.ability.provider.exception.ServiceException;
import org.asiainfo.ability.provider.utils.ClassScaner;
import org.asiainfo.ability.provider.utils.ExceptionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: visen
 * @Date: 2018/3/31
 * @Description:
 */
@Aspect
@Order(-99)
@Component
public class AspectConfig {


    private final static Map<String, ObjectMapper> responseHandlers = new HashMap();
    private final static ObjectMapper mapperNative = new ObjectMapper();
    private final static XmlMapper xmlMapperNative = new XmlMapper();
    static {
        mapperNative.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }
    @Autowired
    private TraceService traceService;
    public static final Logger logger = LogManager.getLogger(AspectConfig.class);

    @Around(value = "@annotation(org.asiainfo.ability.provider.annotation.ApiProvider)")
    public Object ArountTest(ProceedingJoinPoint joinPoint) throws Throwable {
        RequestContext context = RequestContext.getCurrentContext();

        CalleeTrace calleeTrace = (CalleeTrace) context.get(RequestContext.CALLEE_TRACE);

        if (calleeTrace != null) {
            //context.set
            List requestParams = new ArrayList();
            for (Object arg : joinPoint.getArgs()) {
                if (!(arg instanceof ServletRequest || arg instanceof ServletResponse)) {
                    requestParams.add(arg);
                }
                calleeTrace.setRequest(mapperNative.writeValueAsString(requestParams));
            }
        }

        try {
            Object result = joinPoint.proceed();
            if (!(result instanceof String)) {
                Signature signature = joinPoint.getSignature();
                ObjectMapper mapper = responseHandlers.get(signature.getDeclaringTypeName() + signature.getName());
                if(mapper == null) {
                    mapper = mapperNative;
                }

                result = mapper.writeValueAsString(result);
                signature = null;
            }
            calleeTrace.setResponse((String)result);
            return result;

        } catch (Exception e) {
            ExceptionUtils.logTrace(logger, e);
            String responseResult = null;
            if (e instanceof ServiceException) {
                responseResult = ((ServiceException) e).buildErrorInfo();
            } else {
                responseResult = Response.error(ServiceErrorCode.unknonwn.getValue(),
                        ServiceErrorCode.unknonwn.getName());
            }
            return responseResult;
        } finally {
            if (calleeTrace != null) {
                HttpServletResponse servletResponse = (HttpServletResponse) RequestContext.getCurrentContext()
                        .get(RequestContext.RESPONSE_KEY);
                calleeTrace.setEndTime(System.currentTimeMillis());
                servletResponse.setHeader(RequestContext.REQ_TIME, String.valueOf(calleeTrace.getBeginTime()));
                servletResponse.setHeader(RequestContext.RESP_TIME, String.valueOf(calleeTrace.getEndTime()));
                traceService.saveCalleeTrace(calleeTrace);
            }

        }


    }

    public static void loadHanlders() {
        List<Class<?>> controlerClasses = null;
        try {
            controlerClasses = ClassScaner.scanPackage(AnnotationConfig.basePackage
                    , true, RestController.class);
            controlerClasses.addAll(ClassScaner.scanPackage(AnnotationConfig.basePackage
                    , true, AbilityFace.class));
        } catch (IOException e) {
            ExceptionUtils.logTrace(logger, e);
        }
        if (controlerClasses != null) {
            for (Class clazz : controlerClasses) {
                for (Method method : clazz.getDeclaredMethods()) {
                    if (method.isAnnotationPresent(ApiProvider.class)) {
                        //produces = "application/json"
                        RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                        if (requestMapping != null) {
                            String[] produces = requestMapping.produces();
                            responseHandlers.put(clazz.getName() + method.getName(), (produces.length == 0
                                    || produces[0].equals("application/json")) ? mapperNative : xmlMapperNative);
                        }
                    }
                }

            }
        }

    }
}
